#include <math/boundingbox.h>
#include <math/ray.h>

ceBoundingBox::ceBoundingBox()
  : _min (0.0f, 0.0f, 0.0f)
  , _max (0.0f, 0.0f, 0.0f)
  , _center (0.0f, 0.0f, 0.0f)
  , _clean (true)
{
}

void ceBoundingBox::Add(float x, float y, float z)
{
  if (_clean)
    {
      _clean = false;
      _min = ceVector3f(x, y, z);
      _max = ceVector3f(x, y, z);
    }
  else
    {
      _min.x = _min.x > x ? x : _min.x;
      _min.y = _min.y > y ? y : _min.y;
      _min.z = _min.z > z ? z : _min.z;
      _max.x = _max.x < x ? x : _max.x;
      _max.y = _max.y < y ? y : _max.y;
      _max.z = _max.z < z ? z : _max.z;
    }

}

void ceBoundingBox::Add(const ceVector3f &v)
{
  if (_clean)
    {
      _clean = false;
      _min = v;
      _max = v;
    }
  else
    {
      _min.x = _min.x > v.x ? v.x : _min.x;
      _min.y = _min.y > v.y ? v.y : _min.y;
      _min.z = _min.z > v.z ? v.z : _min.z;
      _max.x = _max.x < v.x ? v.x : _max.x;
      _max.y = _max.y < v.y ? v.y : _max.y;
      _max.z = _max.z < v.z ? v.z : _max.z;
    }
}

void ceBoundingBox::Add (const ceBoundingBox& bbox)
{
  for (unsigned i=0; i<8; i++)
    {
      Add (bbox._points[i]);
    }
}

void ceBoundingBox::AddMargin (const ceVector3f& margin)
{
  _min.x -= margin.x;
  _min.y -= margin.y;
  _min.z -= margin.z;
  _max.x += margin.x;
  _max.y += margin.y;
  _max.z += margin.z;
}

void ceBoundingBox::Clear ()
{
  _clean = true;
}

void ceBoundingBox::Add (const ceMatrix4f& base, float x, float y, float z)
{
  Add (base, ceVector3f (x, y, z));
}

void ceBoundingBox::Add(const ceMatrix4f& base, const ceVector3f &v)
{
  ceVector3f p = base * v;
  if (_clean)
    {
      _clean = false;
      _min = p;
      _max = p;
    }
  else
    {
      _min.x = _min.x > p.x ? p.x : _min.x;
      _min.y = _min.y > p.y ? p.y : _min.y;
      _min.z = _min.z > p.z ? p.z : _min.z;
      _max.x = _max.x < p.x ? p.x : _max.x;
      _max.y = _max.y < p.y ? p.y : _max.y;
      _max.z = _max.z < p.z ? p.z : _max.z;
    }
}

void ceBoundingBox::Add (const ceMatrix4f& base, const ceBoundingBox& bbox)
{
  for (unsigned i=0; i<8; i++)
    {
      Add (base * bbox._points[i]);
    }
}


void ceBoundingBox::Update()
{
  _center = _min;
  _center += _max;
  _center /= 2.0f;

  _points[0] = ceVector3f (_min.x, _min.y, _min.z);
  _points[1] = ceVector3f (_min.x, _min.y, _max.z);
  _points[2] = ceVector3f (_min.x, _max.y, _min.z);
  _points[3] = ceVector3f (_min.x, _max.y, _max.z);
  _points[4] = ceVector3f (_max.x, _min.y, _min.z);
  _points[5] = ceVector3f (_max.x, _min.y, _max.z);
  _points[6] = ceVector3f (_max.x, _max.y, _min.z);
  _points[7] = ceVector3f (_max.x, _max.y, _max.z);
}


bool ceBoundingBox::Hit(const ceRay &ray) const
{
  const ceVector3f& d = ray.GetDirection();

  // find the 3d positions on the x, y and z planes and test whether or
  // not they are witin the bounding rect of the bounding box planes


  // test the x planes
  if (d.x != 0.0f)
    {
      ceVector3f p = ray.GetAbsLocationX(_min.x);
      if (_min.y <= p.y && _min.z <= p.z && _max.y >= p.y && _max.z >= p.z)
        {
          return true;
        }
      p = ray.GetAbsLocationX(_max.x);
      if (_min.y <= p.y && _min.z <= p.z && _max.y >= p.y && _max.z >= p.z)
        {
          return true;
        }
    }

  // test the y planes
  if (d.y != 0.0f)
    {
      ceVector3f p = ray.GetAbsLocationY(_min.y);
      if (_min.x <= p.x && _min.z <= p.z && _max.x >= p.x && _max.z >= p.z)
        {
          return true;
        }
      p = ray.GetAbsLocationY(_max.y);
      if (_min.x <= p.x && _min.z <= p.z && _max.x >= p.x && _max.z >= p.z)
        {
          return true;
        }
    }

  // test the z planes
  if (d.z != 0.0f)
    {
      ceVector3f p = ray.GetAbsLocationZ(_min.z);
      if (_min.x <= p.x && _min.y <= p.y && _max.x >= p.x && _max.y >= p.y)
        {
          return true;
        }
      p = ray.GetAbsLocationZ(_max.z);
      if (_min.x <= p.x && _min.y <= p.y && _max.x >= p.x && _max.y >= p.y)
        {
          return true;
        }
    }

  // no of the planes matched...
  return false;
}

